package bola8.entes;

import bola8.geometria.Geometria;
import bola8.geometria.Vector2D;
import bola8.geometria.VectorPolar2D;
import bola8.fuerzas.FuerzaExterna;
import bola8.geometria.Angulo;
import bola8.utilidades.ListaDoble;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Jorge Berjano
 */
public class Mundo {

    private List<Ente> entes;
    private List<Ente> entesEstaticos;
    private List<EnteMovil> entesMoviles;
    public static List<Colision> colisionesEjecutadas = new ArrayList<Colision>();
    public List<FuerzaExterna> fuerzas;
    public double ancho = 500;
    public double alto = 500;
    private Angulo angulo = new Angulo(0);

    public Mundo() {
        entesEstaticos = new ArrayList<Ente>();
        entesMoviles = new ArrayList<EnteMovil>();
        entes = new ListaDoble(entesEstaticos, entesMoviles);
        fuerzas = new ArrayList<FuerzaExterna>();
    }

    public double getAlto() {
        return alto;
    }

    public void setAlto(double alto) {
        this.alto = alto;
    }

    public double getAncho() {
        return ancho;
    }

    public void setAncho(double ancho) {
        this.ancho = ancho;
    }

    public Angulo getAngulo() {
        return angulo;
    }

    public void setAngulo(Angulo angulo) {
        this.angulo = angulo;
    }

    public void agregar(List listaEntes) {
        for (Object ente : listaEntes) {
            agregar(ente);
        }
    }

    public void agregar(Object ente) {
        if (ente instanceof EnteMovil) {
            entesMoviles.add((EnteMovil) ente);
        } else if (ente instanceof Ente) {
            entesEstaticos.add((Ente) ente);
        }

        if (ente instanceof FuerzaExterna) {
            fuerzas.add((FuerzaExterna) ente);
        }
    }

    public void eliminar(Ente ente) {
        entes.remove(ente);
    }

    public void visitarEntesMoviles(VisitadorEntesMoviles visitador) {
        for (EnteMovil enteMovil : entesMoviles) {
            visitador.visitar(enteMovil);
        }
    }

    public void pintar(Graphics2D graphics) {

        for (Ente ente : entes) {
            ente.pintar(graphics);
        }

        for (Colision colision : colisionesEjecutadas) {
            colision.pintar(graphics);
        }
//        graphics.setColor(Color.RED);
//        for (Point2D puntoColision : colisiones) {
//            Point2D punto = puntoColision;
//            graphics.drawOval((int) punto.getX() - 1, (int) punto.getY() - 1, 2, 2);
//        }
    }

    /**
     * Mueve el mundo un instante.
     * Primero se calculan las fuerzas externas que afecan a los entes moviles.
     * Luego se calculan y ejecutan las colisiones...
     * Los movimientos se calculan en base al vector de velocidad de cada ente.
     */
    public void mover() {

        for (EnteMovil enteMovil : entesMoviles) {
            enteMovil.setProcesado(false);
        }

        aplicarFuerzasExternas();

        colisionesEjecutadas.clear();

        Colision colision = calcularProximaColision();
        while (colision != null) {
            colisionesEjecutadas.add(colision);
            colision.ejecutar();
            colision = calcularProximaColision();
        }

//        if (colisionesEjecutadas.size() > 0) {
//            System.out.println("colisiones: " + colisionesEjecutadas.size());
//        }

        for (EnteMovil enteMovil : entesMoviles) {

            if (!enteMovil.isProcesado()) {

                enteMovil.ejecutarFuerzas();
                enteMovil.mover();
            }
            if (esIntrusista(enteMovil)) {
                enteMovil.deshacerMovimiento();
            }
        }
    }

    private void aplicarFuerzasExternas() {
        for (EnteMovil enteMovil : entesMoviles) {
            Vector2D sumaResultantes = new VectorPolar2D();
            for (FuerzaExterna fuerza : fuerzas) {
                Vector2D vector = fuerza.getVector(enteMovil);

                if (vector != null && !vector.esNulo()) {
                    if (fuerza.isAnularResto()) {
                        enteMovil.getVelocidad().setModulo(0);
                        sumaResultantes = vector;
                        break;
                    } else {
                        sumaResultantes = sumaResultantes.sumar(vector);
                    }
                }
            }
            enteMovil.aplicarFuerza(sumaResultantes);

            aplicarFuerzasPlanoInclinado(enteMovil);
        }
    }

    /**
     * Calcula la proxima colisión, que será la que se produzca a menos distancia.
     */
    private Colision calcularProximaColision() {
        Colision proximaColision = null;

        for (EnteMovil enteMovil : entesMoviles) {
            Colision colision = calcularColision(enteMovil);
            if (colision != null
                    && (proximaColision == null || proximaColision.getDistancia() > colision.getDistancia())) {
                proximaColision = colision;
            }
        }
        return proximaColision;
    }

    public void limpiar() {
        entesMoviles.clear();
        entesEstaticos.clear();
        fuerzas.clear();
    }

    private boolean esIntrusista(EnteMovil enteMovil) {
        if (enteMovil.isPermitirIntrusion()) {
            return false;
        }

        for (Ente ente : entes) {
            if (ente != enteMovil && !ente.isPermitirIntrusion()
                    && enteMovil.ocupaEspacioComun(ente)) {
                return true;
            }
        }
        return false;
    }

    public Colision calcularColision(EnteMovil enteMovil) {
        if (Geometria.iguales(enteMovil.getVelocidad().getModulo(), 0)) {
            return null;
        }
        Colision colision = null;
        for (Ente ente : entes) {
            if (ente == enteMovil) {
                continue;
            }
            Colision colisionCandidata = enteMovil.calcularColision(ente);
            if (colisionCandidata == null) {
                continue;
            }
            if (colision == null || colision.getDistancia() > colisionCandidata.getDistancia()) {
                colision = colisionCandidata;
            }
        }
        return colision;
    }

//    private void aplicarFuerzasPlanoInclinado(EnteMovil enteMovil) {
//
//        Vector2D fuerzaResultante = null;
//        for (Ente ente : entesEstaticos) {
//            if (ente == enteMovil) {
//                continue;
//            }
//
//            Vector2D fuerza = enteMovil.calcularFuerzaPlanoInclinado(ente);
//            if (fuerza != null) {
//                // Hay mas de dos planos
//            } else {
//                fuerzaResultante = fuerza;
//            }
//        }
//        if (fuerzaResultante != null) {
//            enteMovil.aplicarFuerza(fuerzaResultante);
//        }
//    }
    private void aplicarFuerzasPlanoInclinado(EnteMovil enteMovil) {

        List<Ente> entesExcluidos = new ArrayList();
        entesExcluidos.add(enteMovil);
        entesExcluidos.add(null);

        for (Ente ente : entes) {
            if (ente == enteMovil) {
                continue;
            }

            Vector2D fuerza = enteMovil.calcularFuerzaPlanoInclinado(ente);
            if (fuerza != null) {
                EnteMovil copiaEnteMovil = (EnteMovil) enteMovil.clone();
                copiaEnteMovil.aplicarFuerza(fuerza);
                entesExcluidos.set(1, ente);
                Vector2D otraFuerza = calcularFuerzasPlanoInclinado(copiaEnteMovil, entesExcluidos);
                // Si no hay mas fuerzas de plano inclinado que afecten al movimiento resultante, se aplica la fuerza
                if (otraFuerza == null) {
                    enteMovil.aplicarFuerza(fuerza);
                    break;
                }
            }
        }
    }

    private Vector2D calcularFuerzasPlanoInclinado(EnteMovil enteMovil, List<Ente> entesExcluidos) {

        for (Ente ente : entes) {
            if (entesExcluidos.contains(ente)) {
                continue;
            }
            Vector2D fuerza = enteMovil.calcularFuerzaPlanoInclinado(ente);
            if (fuerza != null) {
                return fuerza;
            }
        }
        return null;
    }
}
